#!/usr/bin/env python3
# SPDX-License-Identifier: MulanPSL-2.0+
# Copyright (c) 2020 Huawei Technologies Co., Ltd. All rights reserved.


import os
import sys
import subprocess
import traceback
from py_bisect import GitBisect
from bisect_log_config import logger, get_logger

BISECT_STEP_GOOD = 0
BISECT_STEP_BAD = 1
BISECT_STEP_ERROR = -1
BISECT_STEP_SKIP = 125

# gitbisect实例将在main函数中根据session_id创建

class GitBisectRun:
    def __init__(self, job_id, error_id, gitbisect, logger):
        self.job_id = job_id
        self.error_id = error_id
        self.gitbisect = gitbisect
        self.logger = logger

    def init_bisect_commit(self):
        """
        Initialize the bisect job content by fetching the latest commit.

        :return: job{}: A dictionary containing the job content with the latest commit
        """
        job = self.gitbisect.init_job_content(self.job_id)

        # Detect and set task type (critical for error judgment)
        self.gitbisect.is_build_task = self.gitbisect._detect_build_task(job)
        self.logger.info(f"Task type: {'build/compile' if self.gitbisect.is_build_task else 'functional/performance test'}")

        try:
            work_dir = os.getcwd()
            # Support both --no-checkout (bare repo) and normal checkout modes
            try:
                # Try BISECT_HEAD first (for --no-checkout mode)
                commit = subprocess.check_output(
                    ['git', '-C', work_dir, 'rev-parse', 'BISECT_HEAD'],
                    universal_newlines=True
                ).strip()
            except subprocess.CalledProcessError:
                # Fallback to HEAD (for normal checkout mode)
                commit = subprocess.check_output(
                    ['git', '-C', work_dir, 'rev-parse', 'HEAD'],
                    universal_newlines=True
                ).strip()

            if job.get('ss'):
                job['ss']['linux']['commit'] = commit
                # Invalidate cached MD5 for ss jobs
                if 'all_params_md5' in job:
                    del job['all_params_md5']
            elif job['program']['makepkg'].get('commit'):
                job['program']['makepkg']['commit'] = commit

                # CRITICAL: Also update pp structure (used for MD5 calculation)
                if 'pp' in job and 'makepkg' in job['pp']:
                    job['pp']['makepkg']['commit'] = commit
                else:
                    self.logger.warning(f"pp.makepkg not found in job, MD5 may be incorrect!")

                # Invalidate cached MD5 so it gets recalculated with new commit
                if 'all_params_md5' in job:
                    del job['all_params_md5']
        except subprocess.CalledProcessError as e:
            self.logger.exception("Failed to get commit", error=str(e))
            job['upstream_commit'] = None
        return job

    def bisect_submit_job_get_status(self, job):
        """
        Submit a job and get its status during the bisect process.

        :param job: A dictionary containing job details
        :return: Exits with a specific BISECT_STEP_STATUS based on the job status
        """
        try:
            # 直接调用 submit_wait_job_status 方法
            status = self.gitbisect.submit_wait_job_status(job, self.error_id)

            # 根据状态设置退出码
            if not status or status == 'skip':
                sys.exit(BISECT_STEP_SKIP)
            elif status == 'bad':
                sys.exit(BISECT_STEP_BAD)
            elif status == 'good':
                sys.exit(BISECT_STEP_GOOD)
            else:
                self.logger.error("Unknown status", status=status)
                sys.exit(BISECT_STEP_ERROR)
        except Exception:
            self.logger.exception("Error when get_submit_job_status",
                             job_id=getattr(self, 'job_id', 'N/A'),
                             error_id=getattr(self, 'error_id', 'N/A'))
            traceback.print_exc(file=sys.stderr)
            sys.exit(BISECT_STEP_ERROR)

def main():
    try:
        # 尽早获取session_id和基本参数
        if len(sys.argv) < 4:
            raise IndexError("Insufficient parameters")

        result_root = os.path.abspath(sys.argv[3])
        session_id = sys.argv[4] if len(sys.argv) > 4 else None

        # 创建logger实例并确保获取实际的session_id
        logger = get_logger(session_id=session_id, log_dir=result_root)
        actual_session_id = getattr(logger, 'session_id', session_id)
        os.makedirs(result_root, exist_ok=True)

        job_id = sys.argv[1]
        error_id = sys.argv[2]

        # 验证参数有效性
        if not job_id or not job_id.strip():
            raise ValueError("job_id cannot be empty")
        if not error_id or not error_id.strip():
            raise ValueError("error_id cannot be empty")

        logger.context.update({
            'job_id': job_id,
            'error_id': error_id,
            'actual_session_id': actual_session_id
        })

        logger.info(f"Starting bisect step with job_id={job_id}, error_id={error_id}, session_id={actual_session_id}")

        # 使用依赖注入模式创建GitBisect实例
        # logger携带session_id，确保状态一致
        gitbisect = GitBisect(logger=logger)
        gitbisect.set_log(result_root)

        run = GitBisectRun(job_id, error_id, gitbisect, logger)
        job = run.init_bisect_commit()
        run.bisect_submit_job_get_status(job)

    except IndexError:
        error_msg = "ERROR: Insufficient parameters: job_id, error_id, result_root required"
        print(error_msg, file=sys.stderr)
        sys.exit(-1)
    except ValueError as e:
        error_msg = f"ERROR: Invalid parameter: {str(e)}"
        print(error_msg, file=sys.stderr)
        # 如果logger已创建，也记录到日志
        if 'logger' in locals():
            logger.error("Parameter validation failed", error=str(e))
        sys.exit(-1)
    except Exception as e:
        error_msg = f"ERROR: Bisect execution failed: {str(e)}"
        print(error_msg, file=sys.stderr)
        # 如果logger已创建，也记录到日志
        if 'logger' in locals():
            logger.exception("Bisect execution failed", error=str(e))
        traceback.print_exc(file=sys.stderr)
        sys.exit(-1)

if __name__ == "__main__":
    main()